/*
    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

*/

#include "cgpanel/rasterizer/rasterizer.h"
#include "scenemanager/scenemanager.h"
#include "scenemanager/camera/camera.h"
#include "scenemanager/scene/shape.h"
#include "objects/objectlist.h"
#include "objects/object.h"
#include "objects/vertex.h"
#include "objects/plane.h"
#include "cgpanel/floatmatrix.h"
#include "scenemanager/scene/scene.h"
#include <limits>
#include <math.h>
#include <QPainter>
#include "scenemanager/light/pointlight.h"

#include <iostream>
using namespace std;

namespace draw{
Rasterizer::Rasterizer(int w, int h, scene::SceneManager *sceneMgr)
                :m_width(w),m_height(h), m_sceneMgr(sceneMgr)
{
  
  // TODO direction is hard coded in negative z direction
  //set inifinity
  float infinity = sceneMgr->getScene()->cam()->direction()->z();
  infinity = -999999;
  
  for(int i = 0; i <h; ++i){
      m_zBuffer.push_back(new QVector<float>);
      for(int j = 0; j < w; ++j){
          (m_zBuffer[i])->push_back(infinity); 
      }
  }
  
  m_maxZ =  99999; // = far
  m_minZ = -99999; // near
  
  m_viewMatrix = NULL;
  m_canonical = NULL;
  m_final = NULL;
  m_perspective = NULL;
  m_cam = m_sceneMgr->getScene()->cam();
  
  
}
    
Rasterizer::~Rasterizer(){
  for(int i = 0; i < m_zBuffer.size(); ++i)
    m_zBuffer[i]->clear();
  
  m_zBuffer.clear();
  
  if(m_final != NULL)
    delete m_final;
  if(m_viewMatrix != NULL)
    delete m_viewMatrix;
  
  if(m_canonical != NULL)
    delete m_canonical;
}

void Rasterizer::calculateCanonicalBox()
{
  if(m_canonical != NULL)
    delete m_canonical;
  
  if(m_final != NULL)
    delete m_final;
  
  float t =  m_cam->top(m_minZ);
  float r = t;
  float l = -r;
  float b = -t;
 
  // boundaries must be set
  FloatMatrix * temp1 = new FloatMatrix();
  temp1->setIdentity();
  temp1->seta0(((float)m_width)/(float)2);
  temp1->setb1(((float)m_height)/(float)2);
  temp1->seta3((float)(m_width-1)/(float)2);
  temp1->setb3((float)(m_height-1)/(float)2);
  
  //calculate viewmatrix
  FloatMatrix * temp2 = new FloatMatrix();
  temp2->seta0((float)2/(r-l));
  temp2->setb1((float)2/(t-b));
  temp2->setc2((float)2/(m_minZ-m_maxZ));
  temp2->setd3(1);
  
  FloatMatrix * temp3 = new FloatMatrix();
  temp3->setIdentity();
  temp3->seta3(-(l+r)/(float)2);
  temp3->setb3(-(b+t)/(float)2);
  temp3->setc3(-(m_minZ+m_maxZ)/(float)2);
   
  FloatMatrix *temp4 = temp2->multiplyWith(temp3);
  
  m_canonical = temp1->multiplyWith(temp4);
  

  
  delete temp1;
  delete temp2;
  delete temp3;
  delete temp4;
}

void Rasterizer::calculatePerspectiveMatrix()
{
    if(m_perspective != NULL)
      delete m_perspective;
    
    if(m_final != NULL)
      delete m_final;
    
    m_perspective = new FloatMatrix();
  
    // ofwel dit
    m_perspective->seta0((m_minZ));
    m_perspective->setb1((m_minZ));
    m_perspective->setc2( (m_minZ+m_maxZ));
    m_perspective->setc3( -m_maxZ*(m_minZ));
    m_perspective->setd2((float)1  );
    
    //m_VIEWMATRIX SHOULD BE USED 
    FloatMatrix * temp = m_perspective->multiplyWith(m_viewMatrix);   
  
    // calculate final*/
    m_final = m_canonical->multiplyWith(temp);//
    
    delete temp; 
}

void Rasterizer::calculateViewMatrix()
{
  if(m_viewMatrix != NULL)
    delete m_viewMatrix;

    
  FloatMatrix * s1 = new FloatMatrix();
  
  Vertex *W = m_cam->direction()->negative();
  W->setH(0);
  W->normalize();
  Vertex *U = m_cam->up()->vecProd(W);
  U->setH(0);
  U->normalize();
  Vertex * V = W->vecProd(U);
  
  s1->seta0(U->x());
  s1->seta1(U->y());
  s1->seta2(U->z());

  s1->setb0(V->x());
  s1->setb1(V->y());
  s1->setb2(V->z());

  s1->setc0(W->x());
  s1->setc1(W->y());
  s1->setc2(W->z());
  s1->setd3(1);
  
  FloatMatrix * s2 = new FloatMatrix();

  s2->setIdentity();
  s2->seta3(-m_cam->position()->x());
  s2->setb3(-m_cam->position()->y());
  s2->setc3(-m_cam->position()->z());
  
  m_viewMatrix = s1->multiplyWith(s2);
  
  //m_final = m_canonical->multiplyWith(m_viewMatrix);
 
  delete s1;
  delete s2;
  delete U;
  delete V;
  delete W;
}

object::Vertex * Rasterizer::calculateVertex(object::Vertex * val)
{
//   m_final->print();
//   cout << "final:" << endl;
//   cin.get();
  
  return m_final->multiplyWith(val);
}

void Rasterizer::setBoundaries(QVector<object::Plane*>* temp){
  
  for(int i = 0; i< temp->size(); ++i){
         for(int k = 0; k < 3; ++k){
            m_maxZ = min(temp->at(i)->getVertex(k)->z(),m_maxZ);
            m_minZ = max(temp->at(i)->getVertex(k)->z(),m_minZ);          
         }
  }
  cout << "before:" << m_maxZ << " "<< m_minZ <<endl;
  m_maxZ +=200;
  m_minZ -=200;
//   cout << "after:" << m_maxZ << " "<< m_minZ <<endl;
//   cin.get();

}

float Rasterizer::getZBuffer(int yPix, int xPix){
  return (m_zBuffer[yPix])->at(xPix);
}

void Rasterizer::setZBuffer(int yPix, int xPix, float Z){
  m_zBuffer[yPix]->replace(xPix, Z);
}

void Rasterizer::setupPlane(object::Plane * plane)
{
    m_curPlane = plane;
    Vertex * v0 = calculateVertex(plane->getVertex(0));
    v0_org = plane->getVertex(0);

    Vertex * v1 = calculateVertex(plane->getVertex(1));
    v1_org = plane->getVertex(1);
   
    Vertex * v2 = calculateVertex(plane->getVertex(2));
    v2_org = plane->getVertex(2);
    
    ////////////////////////////////////////////////////////////
    v0_h = v0->h();;
    if(v0->h() < 0.01 && v0->h() > 0)
      v0_h = 0.001;
    else if(v0->h() > -0.01 && v0->h() <= 0)
            v0_h = -0.001;
    
    v0_x = v0->x() / v0_h;
    v0_y = v0->y() / v0_h;
    v0_z = v0->z() / v0_h;
    
    ////////////////////////////////////////////////////////////
    v1_h = v1->h();
    if(v1->h() < 0.01 && v1->h() >0)
      v1_h = 0.001;
    else if(v1->h() > -0.01 && v1->h() <=0)
      v1_h = -0.001;
      
    v1_x = v1->x() / v1_h;
    v1_y = v1->y() / v1_h;
    v1_z = v1->z() / v1_h;
    
    ////////////////////////////////////////////////////////////
    v2_h = v2->h();
    if(v2->h() < 0.01 && v2->h() > 0)
      v2_h= 0.001;
    else if(v2->h() > -0.01 && v2->h() <= 0)
      v2_h= -0.001;
    
    v2_x = v2->x() / v2_h;
    v2_y = v2->y() / v2_h;
    v2_z = v2->z() / v2_h;
    
    delete v0;
    delete v1;
    delete v2;
}

void Rasterizer::prepareLoop()
{
  // zoek minimum x en y waarden
  xMin = min(min(v0_x,v1_x),v2_x);
  xMax = max(max(v0_x,v1_x),v2_x);

  yMin = min(min(v0_y,v1_y),v2_y);
  yMax = max(max(v0_y,v1_y),v2_y);

  fGamma = (v0_y - v1_y)*v2_x + (v1_x-v0_x)*v2_y + v0_x*v1_y - v1_x*v0_y;       
  fBeta = (v2_y - v0_y)*v1_x + (v0_x-v2_x)*v1_y+ v2_x*v0_y - v0_x*v2_y;  
  fAlpha = (v1_y - v2_y)*v0_x + (v2_x-v1_x)*v0_y+ v1_x*v2_y - v2_x*v1_y;        
       
  f01_MIN1 = -(v0_y - v1_y) -(v1_x-v0_x)+ v0_x*v1_y - v1_x*v0_y;               
  f20_MIN1 = -(v2_y - v0_y) - (v0_x-v2_x)+ v2_x*v0_y - v0_x*v2_y;               
  f12_MIN1 = -(v1_y - v2_y)- (v2_x-v1_x)+ v1_x*v2_y - v2_x*v1_y;
           
  xMin = max((float)0,(float)floor(xMin));
  yMin = max((float)0,(float)floor(yMin));
  xMax =min((float)m_width-1,xMax);
  yMax = min((float)m_height-1,yMax);
}

void Rasterizer::loopPixels(QPainter * p)
{
for(float yPix = yMin; yPix <= yMax; yPix+=1){
    for(float xPix = xMin; xPix <= xMax;xPix+=1){
      // calculating barycentric coords
      float f01 = (v0_y - v1_y)*xPix + (v1_x-v0_x)*yPix
                    + v0_x*v1_y - v1_x*v0_y;
      float f12 = (v1_y - v2_y)*xPix + (v2_x-v1_x)*yPix
                    + v1_x*v2_y - v2_x*v1_y;
      float f20 = (v2_y - v0_y)*xPix + (v0_x-v2_x)*yPix
                    + v2_x*v0_y - v0_x*v2_y;
                        
      bool degenerate = false;
      if isnan(fAlpha)
        degenerate = true;
      if isinf(fAlpha)
        degenerate = true;
      if isnan(fBeta)
        degenerate = true;
      if isinf(fBeta)
        degenerate = true;
      if isnan(fGamma)
        degenerate = true;
      else if isinf(fGamma)
        degenerate = true;
        
      float alpha = f12/fAlpha;
      float beta = f20/fBeta;
      float gamma = f01/fGamma; 

      if(degenerate)
          continue;

      if(alpha >= 0 && beta >= 0 && gamma >= 0){
        
        if ( ( alpha > 0 || fAlpha*f12_MIN1 >0) &&
              ( beta > 0 || fBeta*f20_MIN1 > 0) &&
              ( gamma > 0 || fGamma*f01_MIN1 > 0) )
        {
          
          float zDepth = alpha*v0_z
                        + beta*v1_z
                        + gamma*v2_z;
                        
          
          if(zDepth >= getZBuffer(yPix, xPix))
          {
            setZBuffer(yPix, xPix, zDepth);
      
            // getSurfaceNormal, do not delete!!!
            Vertex * surfNorm = m_curPlane->getSurfaceNormal(beta,gamma);
                      
            float curX = alpha * v0_org->x() + beta * v1_org->x()
                        + gamma * v2_org->x();
            float curY = alpha * v0_org->y() + beta * v1_org->y()
                        + gamma * v2_org->y();
            float curZ= alpha * v0_org->z() + beta * v1_org->z()
                      + gamma * v2_org->z();
            Vertex * realPosXYZ = new Vertex(curX, curY, curZ);
      
            float nDotl = 0;
            for(int lNr = 0;
                    lNr<m_sceneMgr->getScene()->getNrOfLights();++lNr)
            {
              
              Vertex * lightPos = 
    ((scene::PointLight*)(m_sceneMgr->getScene()->light(lNr)))->pos();
              
              Vertex * lightDir = lightPos->minus(realPosXYZ);
              
              float newNDotL = 0;
              lightDir->normalize();
              newNDotL = surfNorm->dotProd(lightDir);
              nDotl = max(nDotl,newNDotL);
              delete lightDir; 
            } 

            float intensity = 0.8; // TODO
            float colorX = intensity;
            float colorY = intensity;
            float colorZ = intensity;
            if(m_curPlane->getMat() != NULL){
              colorX *= m_curPlane->getMat()->color()->x();
              colorY *= m_curPlane->getMat()->color()->y();
              colorZ *= m_curPlane->getMat()->color()->z();
            }
            nDotl = max((float)0,nDotl);
            nDotl = min((float)1,nDotl);
            m_drawColor.setRgbF(colorX*nDotl,
                                colorY*nDotl,
                                colorZ*nDotl);
            delete realPosXYZ;
            p->setPen(m_drawColor);
            p->drawPoint(m_width- xPix,yPix); 
          } // end if

        } // end if
        
      }// endif
  } // end for
} // end for
 
  
}
    
  
};
